Skip to content

fix: match Claude CLI's project-folder encoding (fixes #38, supersedes #37)#41

Merged
abasiri merged 1 commit into
mainfrom
fix/duplicate-projects-encoding
May 4, 2026
Merged

fix: match Claude CLI's project-folder encoding (fixes #38, supersedes #37)#41
abasiri merged 1 commit into
mainfrom
fix/duplicate-projects-encoding

Conversation

@abasiri

@abasiri abasiri commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Two bugs combined to produce undismissable duplicate project entries in the sidebar (#38). PR #37 partially addressed this for Windows drive paths; this PR fixes the root cause for all platforms by matching Claude CLI's encoding algorithm exactly.

Bug 1 — folder-name encoding mismatch

Switchboard's regex replace(/[/_]/g, '-') only handled / and _. The Claude CLI normalizes every non-alphanumeric character and applies a 200-character cap with a deterministic hash suffix on overflow. So any path containing spaces, dots, backslashes (Windows), colons (Windows drive letters), or parentheses produced two different folder names — Switchboard's seed folder, and the CLI's actual session folder — both surfacing as the same project in the sidebar.

Reverse-engineered the CLI's algorithm from claude 2.1.126:

function encodeProjectPath(p) {
  const s = p.replace(/[^a-zA-Z0-9]/g, '-');
  if (s.length <= 200) return s;
  let h = 0;
  for (let i = 0; i < p.length; i++) h = (h << 5) - h + p.charCodeAt(i) | 0;
  return s.slice(0, 200) + '-' + Math.abs(h).toString(36);
}

The hash is a Java-style String.hashCode truncated to int32. Pure and deterministic, so Switchboard now produces byte-identical folder names to the CLI for any project path.

Bug 2 — phantom projects from archive filter ordering

In session-cache.js:buildProjectsFromCache, the project entry was added to projectMap before the if (!showArchived && s.archived) continue filter ran. Folders whose sessions were all archived ended up as empty { sessions: [] } entries in the sidebar, and the archive button's if (sessions.length === 0) return made them undismissable. Fix: only insert into projectMap after the filter passes.

Changes

  • New encode-project-path.js — single source of truth for the encoder (main process)
  • Mirror of the same function in public/utils.js for the renderer
  • All 10 call sites across main.js, session-cache.js, schedule-ipc.js, public/app.js, public/dialogs.js now use encodeProjectPath()
  • session-cache.js archive-filter ordering fix

Migration note

Existing duplicate folders on disk from before this fix are left alone — auto-cleanup felt risky. Users with pre-existing dupes can rm -rf ~/.claude/projects/<bad-folder-name> manually. New projects added after this fix will encode correctly and merge with whatever the CLI creates.

Relationship to #37

#37 widened the regex to [\\/:_] to cover Windows drive paths. This PR subsumes that fix — [^a-zA-Z0-9] covers \ and : and everything else, and adds the missing 200-char cap + hash suffix branch. Recommend closing #37 in favor of this.

Test plan

  • macOS: add a project with spaces in the path (e.g. /Users/you/My Project) → folder created as -Users-you-My-Project, matches CLI when claude is run there
  • macOS: project path with dots (e.g. msci-6.1-upgrade) → encodes consistently with CLI
  • Windows: add a project on a non-C drive (e.g. Q:\src\DevBox) → folder created as Q--src-DevBox, no ENOENT
  • Archive every session in a project → project disappears from sidebar (no phantom entry)
  • Toggle "show archived" → archived-only projects reappear
  • Empty project directory (no sessions in DB) still appears in sidebar (regression check on the empty-dirs pass)

Fixes #38.

…sidebar entries

Switchboard's folder-name regex only replaced `/` and `_`, while the Claude
CLI replaces every non-alphanumeric character (and applies a 200-char cap
with a hash suffix on overflow). For project paths containing spaces, dots,
backslashes, colons, etc., the two sides produced different folder names
under ~/.claude/projects/, surfacing as undismissable duplicate projects in
the sidebar.

- Add encode-project-path.js mirroring the CLI's algorithm exactly
  (reverse-engineered from claude 2.1.126: /[^a-zA-Z0-9]/g, 200-char cap,
  (h<<5)-h+c|0 hash suffix on overflow). Deterministic and pure.
- Mirror the encoder in public/utils.js for the renderer.
- Route all 10 call sites through encodeProjectPath().
- Fix session-cache.js phantom-project bug: only insert a project entry
  after the archive filter passes, so folders whose sessions are all
  archived don't appear as empty undismissable projects in the sidebar.

Fixes #38. Supersedes #37 (also covers Windows drive paths).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate projects in sidebar

1 participant